#ifndef _QGCM_INTEGRATORS_SRC_
#include "model.h"
#include "ssystem.h"
#include "psystem.h"
#include "qgcmio.h"
#include "integrator.h"
#include "misclib.h"
#include <signal.h>
#include <time.h>

//=============================================================================================//
intoutputinfo::intoutputinfo(){
  this -> nouetssys = false;
  this -> rundiagnosticsblock = true;
  this -> displayLOP = false;
  this -> displayPsys = false;
  this -> nosys = false;
  this -> phidisplayoptions.displayfourierspace = false;
  this -> displayphi = false;
  this -> phidisplayoptions.t_freq = 1;
  this -> phidisplayoptions.t_prime_freq = 1;
  this -> nohup = false;
  this-> phidisplayoptions.equaltimeonly=false;
  this-> psysdisplayoptions.t_freq = true;
}
//=============================================================================================//
volatile sig_atomic_t SIG_TERM = 0;
volatile sig_atomic_t SIG_STATUS = 0;
volatile sig_atomic_t SIG_STATUS_DEBUG = 0; // run status scripts with stepping
volatile sig_atomic_t SIG_RUN_END = 0;
volatile sig_atomic_t SIG_READ_FIFO = 0;
//=============================================================================================//
void catch_signal_while_integrating(int sig){ // signal processor
  // the only thing this function should ever do is set atomically accessible
  // flags which can then be interpreted when the model is ready to do so
  // Do NOT call any functions from this handler!
  // Note: The stack here is different from the program stack and heap memory
  // may not be safetly accessed from here (some exceptions apply)
  if(sig == SIGTERM){
    SIG_TERM++;
    gbl_SIG_INT().toggle();
  }else if(sig == SIGPOLL){ // some systems make it difficult to send a SIGPOLL signal
    SIG_STATUS++;
  }else if(sig == SIGPROF){ // run end script rather than status scripts during an integration
    SIG_RUN_END++;
  }else if(sig == SIGUSR1){
    SIG_READ_FIFO ++;
  }else if(sig == SIGINT){
    gbl_SIG_INT().toggle();
    if(SIG_STATUS_DEBUG > 2) abort(); // control-C pressed multiple times then abort
    SIG_STATUS_DEBUG++; // special higher level status information allows interaction with remote terminal
  };
};
//=============================================================================================//
void process_signals(model_class & mo, const time_t & start_time, const int & tindex){
  if( SIG_TERM > 0 ){
    time_t cur_time; time(&cur_time);
    mo.printfmsg( "'TERM' signal received @ timestep %i after %f seconds.\n", tindex, difftime(cur_time, start_time));
    mo.printfmsg( "Running END script and terminating.\n");
  }
  if( SIG_READ_FIFO > 0 ){
    // create and read fifo 
    time_t cur_time; time(&cur_time);
    mo.printfmsg("'USR1' signal received @ timestep %i after %f seconds.\n", tindex, difftime(cur_time, start_time));
    const char * fifoname = mo.get_global_param_by_name(gdb_str_MODEL_FIFO).getsValue();
    mo.printfmsg("Creating fifo '%s' for reading and waiting for user input.\n", fifoname);
    if(! mo.readfifo()){
      mo.printfmsg("Error: fifo read failed.\n");
    }
    SIG_READ_FIFO = 0;
  }
  if( SIG_STATUS > 0 ){
    time_t cur_time; time(&cur_time);
    mo.printfmsg( "'POLL' signal received @ timestep %i after %f seconds.\n", tindex, difftime(cur_time, start_time));
    mo.printfmsg( "Running STATUS Script.\n");
    mo.runstatusblocks(false); // do not set break points
    mo.flushmessages();
    mo.flushstdout();
    SIG_STATUS=0;
  }
  if(SIG_STATUS_DEBUG > 0){
    SIG_STATUS_DEBUG=0;
    time_t cur_time; time(&cur_time);
    mo.printfmsg( "'INT' signal received @ timestep %i after %f seconds.\n", tindex, difftime(cur_time, start_time));
    mo.printfmsg( "Running STATUS Script in debug mode.\n");
    bool Ivalid = mo.isdebuginputvalid();
    if( ! Ivalid ){
      mo.printfmsg("Model has no valid input stream. STATUS script is being run without debugger.\n");
    }
    mo.runstatusblocks(Ivalid); // sets a break point on in the parser control
    mo.flushmessages();
    mo.flushstdout();
  }
  if( SIG_RUN_END > 0 ){
    time_t cur_time; time(&cur_time);
    mo.printfmsg( "'PROF' signal received @ timestep %i after %f seconds.\n", tindex, difftime(cur_time, start_time));
    mo.printfmsg( "Running the END scripts without ending the integration.\n");
    mo.rundiagnosticblocks(false, true); // run the end script during integration
    mo.flushmessages();
    mo.flushstdout();
    SIG_RUN_END=0;
  }
};
//=============================================================================================//
void pintegrate(model_class & mo, const unsigned int & nsteps, const intoutputinfo & options){
  unsigned int iter = 0;
  const qstream & out = options.outfile;
  REAL t = 0;
  const vector<REAL> & kdomain_centered = mo.get_global_prof_by_name(gdb_prof_str_K_DOMAIN_CENTERED).getData();
  const vector<int> & nkdomain = mo.get_global_prof_by_name(gdb_prof_str_NK_DOMAIN).getiData();
  bool nonlinear = mo.get_global_param_by_name(gdb_str_NONLINEAR).getiValue() != 0;
  const int iter_tstep = mo.get_global_param_index_from_name(gdb_str_TIMESTEP); 
  psystem & p = mo.getPsystem();
  ssystem & ssys = mo.getSsystem();


  const int numsteps = ssys.get_tindex() + nsteps;

  p.init_tindex(numsteps+1);
  // run diagnostics blocks
  mo.clearspreprocdata();
  time_t start_time; time(&start_time); // store the initial time
  mo.printfmsg("Initializing the S-system.\n");

  if(options.tprimeintegrations.size() > 0){
    mo.getSsystem().settprimeintegrationlist( & ( options.tprimeintegrations ) );
  }
  if(options.nouetssys){
    mo.getSsystem().setstepequaltimeonly(true);
  }

  ssys.setnumbertimesteps(numsteps+1);
  if(options.rundiagnosticsblock){
    mo.printfmsg( "Running the START scripts.\n");
    mo.rundiagnosticblocks(true, false); // run start blocks
  }
  // setup signals to catch
  if( options.nohup ){
    // redirect stdin and stderr to nohup.stdout, nohup.stderr
    redirectoutputstreamsfornohup(); // throws an exception on failure 
    signal(SIGHUP, catch_signal_while_integrating);
  }
  signal(SIGTERM, catch_signal_while_integrating);
  signal(SIGINT, catch_signal_while_integrating);
  signal(SIGPOLL, catch_signal_while_integrating);
  signal(SIGUSR1, catch_signal_while_integrating);
  signal(SIGPROF, catch_signal_while_integrating);
  signal(SIGUSR2, catch_signal_while_integrating);

  mo.printfmsg( "Beginning integration with %i worker threads.\n", mo.get_num_threads());

  while(p.get_tindex() < numsteps && ( SIG_TERM == 0 ) ){
    const REAL timestep = mo.get_global_param_by_index(iter_tstep).getfValue();
    if(iter % options.phidisplayoptions.t_freq == 0){
      if(options.displayPsys) out.printf("\ttstep = %i", iter);
      if(options.displayPsys){
        for(unsigned int i = 0; i < p.getmeanfieldsize(); i++){
          out.printf( ", %s = %lf", p.getmeanfieldname(i).c_str());
          out << p.getmeanfield(i);
        }
      }
      if(options.displayPsys) out.printf("\n");
    }
    try{ 
      p.stepforward(timestep, options.nosys, nonlinear);
      if(options.rundiagnosticsblock) mo.rundiagnosticblocks(false, false); // run repeat blocks
    }catch(const cgcmexception & ex){
      if( is_exception_SIG_INT(ex) ){
        gbl_SIG_INT().reset();
        ssys.sync_tindex(p.get_tindex());	
      }else{
        throw ex;
      }
    }
    process_signals( mo, start_time, p.get_tindex()  );
    t+=timestep;
  }
  time_t end_time;
  time(&end_time);
  double tdiff = difftime( end_time, start_time );
  mo.printfmsg( "Integration completed after %f seconds.\n", tdiff); 
  if(options.rundiagnosticsblock){
    mo.printfmsg( "Running END scripts.\n");
    mo.rundiagnosticblocks(false, true); // run end blocks
  }
  mo.flushmessages();
  mo.flushstdout();
}


#endif
